package com.yunkeji.common.rtc;

import java.util.Arrays;
import java.util.TreeMap;

public class AccessToken {
    public enum Privileges {
        PrivPublishStream(0),

        // not exported, do not use directly
        privPublishAudioStream(1),
        privPublishVideoStream(2),
        privPublishDataStream(3),

        PrivSubscribeStream(4);

        public short intValue;

        Privileges(int value) {
            intValue = (short) value;
        }
    }

    public String appID;
    public String appKey;
    public String roomID;
    public String userID;
    public int issuedAt;
    public int expireAt;
    public int nonce;
    public TreeMap<Short, Integer> privileges;

    public byte[] signature;

    // Initializes token struct by required parameters.
    public AccessToken(String appID, String appKey, String roomID, String userID) {
        this.appID = appID;
        this.appKey = appKey;
        this.roomID = roomID;
        this.userID = userID;
        this.issuedAt = Utils.getTimestamp();
        this.nonce = Utils.randomInt();
        this.privileges = new TreeMap<>();
    }

    public static String getVersion() {
        return "001";
    }

    // AddPrivilege adds permission for token with an expiration.
    public void AddPrivilege(Privileges privilege, int expireTimestamp) {
        this.privileges.put(privilege.intValue, expireTimestamp);

        if (privilege.intValue == Privileges.PrivPublishStream.intValue) {
            this.privileges.put(Privileges.privPublishVideoStream.intValue, expireTimestamp);
            this.privileges.put(Privileges.privPublishAudioStream.intValue, expireTimestamp);
            this.privileges.put(Privileges.privPublishDataStream.intValue, expireTimestamp);
        }
    }

    // ExpireTime sets token expire time, won't expire by default.
    // The token will be invalid after expireTime no matter what privilege's expireTime is.
    public void ExpireTime(int expireTimestamp) {
        this.expireAt = expireTimestamp;
    }

    public byte[] packMsg() {
        ByteBuf buffer = new ByteBuf();

        return buffer.put(this.nonce)
                .put(this.issuedAt)
                .put(this.expireAt)
                .put(this.roomID)
                .put(this.userID)
                .putIntMap(this.privileges)
                .asBytes();
    }

    // Serialize generates the token string
    public String Serialize() {
        byte[] msg = this.packMsg();
        try {
            this.signature = Utils.hmacSign(this.appKey, msg);
        } catch (Exception e) {
            e.printStackTrace();
        }

        ByteBuf buffer = new ByteBuf();
        byte[] content = buffer.put(msg).put(signature).asBytes();

        return getVersion() + this.appID + Utils.base64Encode(content);
    }

    // Verify checks if this token valid, called by server side.
    public boolean Verify(String key) {
        if (this.expireAt > 0 && Utils.getTimestamp() > this.expireAt) {
            return false;
        }

        this.appKey = key;

        byte[] signature;

        try {
            signature = Utils.hmacSign(this.appKey, this.packMsg());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return Arrays.equals(this.signature, signature);
    }

    // Parse retrieves token information from raw string
    public static AccessToken Parse(String raw) {
        AccessToken token = new AccessToken("", "", "", "");

        if (raw.length() <= Utils.VERSION_LENGTH + Utils.APP_ID_LENGTH) {
            return token;
        }

        if (!getVersion().equals(raw.substring(0, Utils.VERSION_LENGTH))) {
            return token;
        }

        token.appID =
                raw.substring(Utils.VERSION_LENGTH, Utils.VERSION_LENGTH + Utils.APP_ID_LENGTH);
        byte[] content =
                Utils.base64Decode(
                        raw.substring(Utils.VERSION_LENGTH + Utils.APP_ID_LENGTH, raw.length()));

        ByteBuf buffer = new ByteBuf(content);
        byte[] msg = buffer.readBytes();
        token.signature = buffer.readBytes();

        ByteBuf msgBuf = new ByteBuf(msg);
        token.nonce = msgBuf.readInt();
        token.issuedAt = msgBuf.readInt();
        token.expireAt = msgBuf.readInt();
        token.roomID = msgBuf.readString();
        token.userID = msgBuf.readString();
        token.privileges = msgBuf.readIntMap();

        return token;
    }

    @Override
    public String toString() {
        return "AccessToken{"
                + "appID='"
                + appID
                + '\''
                + ", appKey='"
                + appKey
                + '\''
                + ", roomID='"
                + roomID
                + '\''
                + ", userID='"
                + userID
                + '\''
                + ", issuedAt="
                + issuedAt
                + ", expireAt="
                + expireAt
                + ", nonce="
                + nonce
                + ", privileges="
                + privileges
                + ", signature="
                + Arrays.toString(signature)
                + '}';
    }
}
